/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

package compiler.impl.p3;

import compiler.api.a1.AnalysisResult;
import compiler.api.a1.RuleConditionBuilder;
import compiler.impl.p1.BaseDescr;
import compiler.impl.p1.BoundIdentifiers;
import compiler.impl.p1.EvalDescr;
import compiler.impl.p1.PatternBuilder;
import compiler.impl.p1.RuleBuildContext;
import compiler.impl.p2.rule.builder.dialect.java.JavaRuleBuilderHelper;
import core.api.a1.RuleConditionElement;
import core.impl.p1.Declaration;
import core.impl.p1.Pattern;
import core.impl.p4.RuleTerminalNode;
import core.impl.p5.DeclarationScopeResolver;
import core.impl.p5.EvalCondition;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public abstract class AbstractASMEvalBuilder implements RuleConditionBuilder {
    public RuleConditionElement build(RuleBuildContext context, BaseDescr descr) {
        // it must be an EvalDescr
        final EvalDescr evalDescr = (EvalDescr) descr;

        Map<String, Declaration> decls = context.getDeclarationResolver().getDeclarations( context.getRule() );

        AnalysisResult analysis = context.getDialect().analyzeExpression( context,
                                                                          evalDescr,
                                                                          evalDescr.getContent(),
                                                                          new BoundIdentifiers( DeclarationScopeResolver.getDeclarationClasses(decls),
                                                                                                context ) );

        List<Declaration> requiredDeclarations = new ArrayList<Declaration>();
        for (String usedIdentifier : analysis.getIdentifiers()) {
            Declaration usedDec = decls.get(usedIdentifier);
            if (usedDec != null) {
                requiredDeclarations.add(usedDec);
            }
        }

        final Declaration[] declarations = requiredDeclarations.toArray( new Declaration[requiredDeclarations.size()]);
        return buildEval(context, evalDescr, analysis, declarations);
     }

    public RuleConditionElement build(RuleBuildContext context, BaseDescr descr, Pattern prefixPattern) {
        if (prefixPattern == null) {
            return build(context, descr);
        }

        EvalDescr evalDescr = (EvalDescr) descr;

        PredicateDescr predicateDescr = new PredicateDescr( context.getRuleDescr().getResource(), evalDescr.getContent() );
        AnalysisResult analysis = PatternBuilder.buildAnalysis(context, prefixPattern, predicateDescr, null);

        Declaration[] declarations = getUsedDeclarations(context, prefixPattern, analysis);
        return buildEval(context, evalDescr, analysis, declarations);
    }

    private RuleConditionElement buildEval(RuleBuildContext context, EvalDescr evalDescr, AnalysisResult analysis, Declaration[] declarations) {
        String className = "eval" + context.getNextId();
        evalDescr.setClassMethodName( className );

        Arrays.sort(declarations, RuleTerminalNode.SortDeclarations.instance);

        EvalCondition eval = new EvalCondition( declarations );

        Map<String, Object> vars = JavaRuleBuilderHelper.createVariableContext(className,
                (String) evalDescr.getContent(),
                context,
                declarations,
                null,
                analysis.getBoundIdentifiers().getGlobals());

        JavaRuleBuilderHelper.generateMethodTemplate("evalMethod", context, vars);

        byte[] bytecode = createEvalBytecode(context, vars);
        JavaRuleBuilderHelper.registerInvokerBytecode(context, vars, bytecode, eval);
        return eval;
    }

    private Declaration[] getUsedDeclarations(RuleBuildContext context, Pattern pattern, AnalysisResult analysis) {
        BoundIdentifiers usedIdentifiers = analysis.getBoundIdentifiers();
        final List<Declaration> declarations = new ArrayList<Declaration>();

        for ( String id : usedIdentifiers.getDeclrClasses().keySet() ) {
            declarations.add( context.getDeclarationResolver().getDeclaration( id ) );
        }

        PatternBuilder.createImplicitBindings(context,
                pattern,
                analysis.getNotBoundedIdentifiers(),
                analysis.getBoundIdentifiers(),
                declarations);

        return declarations.toArray( new Declaration[declarations.size()] );
    }

    protected abstract byte[] createEvalBytecode(RuleBuildContext context, Map vars);
}
